baubs git
Commit 3ca5edf4dc7e9c1d2006e47da44c600c2f0e59f6
Parents : 7cf4a5d
Author : Jan-Henrik Bruhn <jan-henrik.bruhn@offis.de>
Date : 2025-12-20T23:03:38+01:00
fix: Run prettier formatting on all components
- Applied prettier auto-formatting to all component and utility files
- Fixed semicolons, commas, and indentation throughout codebase
- No functional changes, only code style improvements
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Changes
17 files changed, 570 insertions(+), 557 deletions(-)
Diff
diff --git a/src/components/AppHeader.tsx b/src/components/AppHeader.tsx
index 236a799..86eb809 100644
--- a/src/components/AppHeader.tsx
+++ b/src/components/AppHeader.tsx
@@ -167,7 +167,7 @@ export function AppHeader() {
"gap-1.5 flex-shrink-0",
machineErrorMessage || pyodideError
? "animate-pulse hover:animate-none"
- : "invisible pointer-events-none"
+ : "invisible pointer-events-none",
)}
title="Click to view error details"
aria-label="View error details"
diff --git a/src/components/FileUpload.tsx b/src/components/FileUpload.tsx
index e0004b8..010f815 100644
--- a/src/components/FileUpload.tsx
+++ b/src/components/FileUpload.tsx
@@ -233,188 +233,188 @@ export function FileUpload() {
</div>
</div>
- {resumeAvailable && resumeFileName && (
- <div className="bg-success-50 dark:bg-success-900/20 border border-success-200 dark:border-success-800 px-3 py-2 rounded mb-3">
- <p className="text-xs text-success-800 dark:text-success-200">
- <strong>Cached:</strong> "{resumeFileName}"
- </p>
- </div>
- )}
+ {resumeAvailable && resumeFileName && (
+ <div className="bg-success-50 dark:bg-success-900/20 border border-success-200 dark:border-success-800 px-3 py-2 rounded mb-3">
+ <p className="text-xs text-success-800 dark:text-success-200">
+ <strong>Cached:</strong> "{resumeFileName}"
+ </p>
+ </div>
+ )}
+
+ {isLoading && <PatternInfoSkeleton />}
+
+ {!isLoading && pesData && (
+ <div className="mb-3">
+ <PatternInfo pesData={pesData} showThreadBlocks />
+ </div>
+ )}
- {isLoading && <PatternInfoSkeleton />}
+ <div className="flex gap-2 mb-3">
+ <input
+ type="file"
+ accept=".pes"
+ onChange={handleFileChange}
+ id="file-input"
+ className="hidden"
+ disabled={isLoading || patternUploaded || isUploading}
+ />
+ <Button
+ asChild={!fileService.hasNativeDialogs()}
+ onClick={
+ fileService.hasNativeDialogs()
+ ? () => handleFileChange()
+ : undefined
+ }
+ disabled={isLoading || patternUploaded || isUploading}
+ variant="outline"
+ className="flex-[2]"
+ >
+ {fileService.hasNativeDialogs() ? (
+ <>
+ {isLoading ? (
+ <>
+ <Loader2 className="w-3.5 h-3.5 animate-spin" />
+ <span>Loading...</span>
+ </>
+ ) : patternUploaded ? (
+ <>
+ <CheckCircleIcon className="w-3.5 h-3.5" />
+ <span>Locked</span>
+ </>
+ ) : (
+ <>
+ <FolderOpenIcon className="w-3.5 h-3.5" />
+ <span>Choose PES File</span>
+ </>
+ )}
+ </>
+ ) : (
+ <label htmlFor="file-input" className="flex items-center gap-2">
+ {isLoading ? (
+ <>
+ <Loader2 className="w-3.5 h-3.5 animate-spin" />
+ <span>Loading...</span>
+ </>
+ ) : patternUploaded ? (
+ <>
+ <CheckCircleIcon className="w-3.5 h-3.5" />
+ <span>Locked</span>
+ </>
+ ) : (
+ <>
+ <FolderOpenIcon className="w-3.5 h-3.5" />
+ <span>Choose PES File</span>
+ </>
+ )}
+ </label>
+ )}
+ </Button>
- {!isLoading && pesData && (
- <div className="mb-3">
- <PatternInfo pesData={pesData} showThreadBlocks />
+ {pesData &&
+ canUploadPattern(machineStatus) &&
+ !patternUploaded &&
+ uploadProgress < 100 && (
+ <Button
+ onClick={handleUpload}
+ disabled={!isConnected || isUploading || !boundsCheck.fits}
+ className="flex-1"
+ aria-label={
+ isUploading
+ ? `Uploading pattern: ${uploadProgress.toFixed(0)}% complete`
+ : boundsCheck.error || "Upload pattern to machine"
+ }
+ >
+ {isUploading ? (
+ <>
+ <Loader2 className="w-3.5 h-3.5 animate-spin" />
+ {uploadProgress > 0
+ ? uploadProgress.toFixed(0) + "%"
+ : "Uploading"}
+ </>
+ ) : (
+ <>
+ <ArrowUpTrayIcon className="w-3.5 h-3.5" />
+ Upload
+ </>
+ )}
+ </Button>
+ )}
</div>
- )}
- <div className="flex gap-2 mb-3">
- <input
- type="file"
- accept=".pes"
- onChange={handleFileChange}
- id="file-input"
- className="hidden"
- disabled={isLoading || patternUploaded || isUploading}
- />
- <Button
- asChild={!fileService.hasNativeDialogs()}
- onClick={
- fileService.hasNativeDialogs()
- ? () => handleFileChange()
- : undefined
- }
- disabled={isLoading || patternUploaded || isUploading}
- variant="outline"
- className="flex-[2]"
+ {/* Pyodide initialization progress indicator - shown when initializing or waiting */}
+ {!pyodideReady && pyodideProgress > 0 && (
+ <div className="mb-3">
+ <div className="flex justify-between items-center mb-1.5">
+ <span className="text-xs font-medium text-gray-600 dark:text-gray-400">
+ {isLoading && !pyodideReady
+ ? "Please wait - initializing Python environment..."
+ : pyodideLoadingStep || "Initializing Python environment..."}
+ </span>
+ <span className="text-xs font-bold text-primary-600 dark:text-primary-400">
+ {pyodideProgress.toFixed(0)}%
+ </span>
+ </div>
+ <Progress value={pyodideProgress} className="h-2.5" />
+ <p className="text-xs text-gray-500 dark:text-gray-400 mt-1.5 italic">
+ {isLoading && !pyodideReady
+ ? "File dialog will open automatically when ready"
+ : "This only happens once on first use"}
+ </p>
+ </div>
+ )}
+
+ {/* Error/warning messages with smooth transition - placed after buttons */}
+ <div
+ className="transition-all duration-200 ease-in-out overflow-hidden"
+ style={{
+ maxHeight:
+ pesData && (boundsCheck.error || !canUploadPattern(machineStatus))
+ ? "200px"
+ : "0px",
+ marginTop:
+ pesData && (boundsCheck.error || !canUploadPattern(machineStatus))
+ ? "12px"
+ : "0px",
+ }}
>
- {fileService.hasNativeDialogs() ? (
- <>
- {isLoading ? (
- <>
- <Loader2 className="w-3.5 h-3.5 animate-spin" />
- <span>Loading...</span>
- </>
- ) : patternUploaded ? (
- <>
- <CheckCircleIcon className="w-3.5 h-3.5" />
- <span>Locked</span>
- </>
- ) : (
- <>
- <FolderOpenIcon className="w-3.5 h-3.5" />
- <span>Choose PES File</span>
- </>
- )}
- </>
- ) : (
- <label htmlFor="file-input" className="flex items-center gap-2">
- {isLoading ? (
- <>
- <Loader2 className="w-3.5 h-3.5 animate-spin" />
- <span>Loading...</span>
- </>
- ) : patternUploaded ? (
- <>
- <CheckCircleIcon className="w-3.5 h-3.5" />
- <span>Locked</span>
- </>
- ) : (
- <>
- <FolderOpenIcon className="w-3.5 h-3.5" />
- <span>Choose PES File</span>
- </>
- )}
- </label>
+ {pesData && !canUploadPattern(machineStatus) && (
+ <Alert className="bg-warning-100 dark:bg-warning-900/20 border-warning-200 dark:border-warning-800">
+ <AlertDescription className="text-warning-800 dark:text-warning-200 text-sm">
+ Cannot upload while {getMachineStateCategory(machineStatus)}
+ </AlertDescription>
+ </Alert>
)}
- </Button>
- {pesData &&
- canUploadPattern(machineStatus) &&
- !patternUploaded &&
- uploadProgress < 100 && (
- <Button
- onClick={handleUpload}
- disabled={!isConnected || isUploading || !boundsCheck.fits}
- className="flex-1"
- aria-label={
- isUploading
- ? `Uploading pattern: ${uploadProgress.toFixed(0)}% complete`
- : boundsCheck.error || "Upload pattern to machine"
- }
+ {pesData && boundsCheck.error && (
+ <Alert
+ variant="destructive"
+ className="bg-danger-100 dark:bg-danger-900/20 border-danger-200 dark:border-danger-800"
>
- {isUploading ? (
- <>
- <Loader2 className="w-3.5 h-3.5 animate-spin" />
- {uploadProgress > 0
- ? uploadProgress.toFixed(0) + "%"
- : "Uploading"}
- </>
- ) : (
- <>
- <ArrowUpTrayIcon className="w-3.5 h-3.5" />
- Upload
- </>
- )}
- </Button>
+ <AlertDescription className="text-danger-800 dark:text-danger-200 text-sm">
+ <strong>Pattern too large:</strong> {boundsCheck.error}
+ </AlertDescription>
+ </Alert>
)}
- </div>
-
- {/* Pyodide initialization progress indicator - shown when initializing or waiting */}
- {!pyodideReady && pyodideProgress > 0 && (
- <div className="mb-3">
- <div className="flex justify-between items-center mb-1.5">
- <span className="text-xs font-medium text-gray-600 dark:text-gray-400">
- {isLoading && !pyodideReady
- ? "Please wait - initializing Python environment..."
- : pyodideLoadingStep || "Initializing Python environment..."}
- </span>
- <span className="text-xs font-bold text-primary-600 dark:text-primary-400">
- {pyodideProgress.toFixed(0)}%
- </span>
- </div>
- <Progress value={pyodideProgress} className="h-2.5" />
- <p className="text-xs text-gray-500 dark:text-gray-400 mt-1.5 italic">
- {isLoading && !pyodideReady
- ? "File dialog will open automatically when ready"
- : "This only happens once on first use"}
- </p>
</div>
- )}
-
- {/* Error/warning messages with smooth transition - placed after buttons */}
- <div
- className="transition-all duration-200 ease-in-out overflow-hidden"
- style={{
- maxHeight:
- pesData && (boundsCheck.error || !canUploadPattern(machineStatus))
- ? "200px"
- : "0px",
- marginTop:
- pesData && (boundsCheck.error || !canUploadPattern(machineStatus))
- ? "12px"
- : "0px",
- }}
- >
- {pesData && !canUploadPattern(machineStatus) && (
- <Alert className="bg-warning-100 dark:bg-warning-900/20 border-warning-200 dark:border-warning-800">
- <AlertDescription className="text-warning-800 dark:text-warning-200 text-sm">
- Cannot upload while {getMachineStateCategory(machineStatus)}
- </AlertDescription>
- </Alert>
- )}
-
- {pesData && boundsCheck.error && (
- <Alert
- variant="destructive"
- className="bg-danger-100 dark:bg-danger-900/20 border-danger-200 dark:border-danger-800"
- >
- <AlertDescription className="text-danger-800 dark:text-danger-200 text-sm">
- <strong>Pattern too large:</strong> {boundsCheck.error}
- </AlertDescription>
- </Alert>
- )}
- </div>
- {isUploading && uploadProgress < 100 && (
- <div className="mt-3">
- <div className="flex justify-between items-center mb-1.5">
- <span className="text-xs font-medium text-gray-600 dark:text-gray-400">
- Uploading
- </span>
- <span className="text-xs font-bold text-secondary-600 dark:text-secondary-400">
- {uploadProgress > 0
- ? uploadProgress.toFixed(1) + "%"
- : "Starting..."}
- </span>
+ {isUploading && uploadProgress < 100 && (
+ <div className="mt-3">
+ <div className="flex justify-between items-center mb-1.5">
+ <span className="text-xs font-medium text-gray-600 dark:text-gray-400">
+ Uploading
+ </span>
+ <span className="text-xs font-bold text-secondary-600 dark:text-secondary-400">
+ {uploadProgress > 0
+ ? uploadProgress.toFixed(1) + "%"
+ : "Starting..."}
+ </span>
+ </div>
+ <Progress
+ value={uploadProgress}
+ className="h-2.5 [&>div]:bg-gradient-to-r [&>div]:from-secondary-500 [&>div]:via-secondary-600 [&>div]:to-secondary-700 dark:[&>div]:from-secondary-600 dark:[&>div]:via-secondary-700 dark:[&>div]:to-secondary-800"
+ />
</div>
- <Progress
- value={uploadProgress}
- className="h-2.5 [&>div]:bg-gradient-to-r [&>div]:from-secondary-500 [&>div]:via-secondary-600 [&>div]:to-secondary-700 dark:[&>div]:from-secondary-600 dark:[&>div]:via-secondary-700 dark:[&>div]:to-secondary-800"
- />
- </div>
- )}
+ )}
</CardContent>
</Card>
);
diff --git a/src/components/PatternInfo.tsx b/src/components/PatternInfo.tsx
index 04256ac..a8fcc20 100644
--- a/src/components/PatternInfo.tsx
+++ b/src/components/PatternInfo.tsx
@@ -23,10 +23,18 @@ export function PatternInfo({
<Tooltip>
<TooltipTrigger asChild>
<div className="bg-gray-200 dark:bg-gray-700/50 p-2 rounded cursor-help">
- <span className="text-gray-600 dark:text-gray-400 block">Size</span>
+ <span className="text-gray-600 dark:text-gray-400 block">
+ Size
+ </span>
<span className="font-semibold text-gray-900 dark:text-gray-100">
- {((pesData.bounds.maxX - pesData.bounds.minX) / 10).toFixed(1)} x{" "}
- {((pesData.bounds.maxY - pesData.bounds.minY) / 10).toFixed(1)} mm
+ {((pesData.bounds.maxX - pesData.bounds.minX) / 10).toFixed(
+ 1,
+ )}{" "}
+ x{" "}
+ {((pesData.bounds.maxY - pesData.bounds.minY) / 10).toFixed(
+ 1,
+ )}{" "}
+ mm
</span>
</div>
</TooltipTrigger>
@@ -45,7 +53,8 @@ export function PatternInfo({
{pesData.penStitches?.stitches.length.toLocaleString() ||
pesData.stitchCount.toLocaleString()}
{pesData.penStitches &&
- pesData.penStitches.stitches.length !== pesData.stitchCount && (
+ pesData.penStitches.stitches.length !==
+ pesData.stitchCount && (
<span
className="text-gray-500 dark:text-gray-500 font-normal ml-1"
title="Input stitch count from PES file (lock stitches were added for machine compatibility)"
@@ -99,64 +108,64 @@ export function PatternInfo({
<TooltipProvider>
<div className="flex gap-1">
{pesData.uniqueColors.slice(0, 8).map((color, idx) => {
- // Primary metadata: brand and catalog number
- const primaryMetadata = [
- color.brand,
- color.catalogNumber ? `#${color.catalogNumber}` : null,
- ]
- .filter(Boolean)
- .join(" ");
+ // Primary metadata: brand and catalog number
+ const primaryMetadata = [
+ color.brand,
+ color.catalogNumber ? `#${color.catalogNumber}` : null,
+ ]
+ .filter(Boolean)
+ .join(" ");
- // Secondary metadata: chart and description
- const secondaryMetadata = [color.chart, color.description]
- .filter(Boolean)
- .join(" ");
+ // Secondary metadata: chart and description
+ const secondaryMetadata = [color.chart, color.description]
+ .filter(Boolean)
+ .join(" ");
- const metadata = [primaryMetadata, secondaryMetadata]
- .filter(Boolean)
- .join(" • ");
+ const metadata = [primaryMetadata, secondaryMetadata]
+ .filter(Boolean)
+ .join(" • ");
- // Show which thread blocks use this color in PatternSummaryCard
- const threadNumbers = color.threadIndices
- .map((i) => i + 1)
- .join(", ");
- const tooltipText = showThreadBlocks
- ? metadata
- ? `Color ${idx + 1}: ${color.hex} - ${metadata}`
- : `Color ${idx + 1}: ${color.hex}`
- : metadata
- ? `Color ${idx + 1}: ${color.hex}\n${metadata}\nUsed in thread blocks: ${threadNumbers}`
- : `Color ${idx + 1}: ${color.hex}\nUsed in thread blocks: ${threadNumbers}`;
+ // Show which thread blocks use this color in PatternSummaryCard
+ const threadNumbers = color.threadIndices
+ .map((i) => i + 1)
+ .join(", ");
+ const tooltipText = showThreadBlocks
+ ? metadata
+ ? `Color ${idx + 1}: ${color.hex} - ${metadata}`
+ : `Color ${idx + 1}: ${color.hex}`
+ : metadata
+ ? `Color ${idx + 1}: ${color.hex}\n${metadata}\nUsed in thread blocks: ${threadNumbers}`
+ : `Color ${idx + 1}: ${color.hex}\nUsed in thread blocks: ${threadNumbers}`;
- return (
- <Tooltip key={idx}>
+ return (
+ <Tooltip key={idx}>
+ <TooltipTrigger asChild>
+ <div
+ className="w-3 h-3 rounded-full border border-gray-300 dark:border-gray-600 cursor-help"
+ style={{ backgroundColor: color.hex }}
+ />
+ </TooltipTrigger>
+ <TooltipContent className="max-w-xs">
+ <p className="text-xs whitespace-pre-line">{tooltipText}</p>
+ </TooltipContent>
+ </Tooltip>
+ );
+ })}
+ {pesData.uniqueColors.length > 8 && (
+ <Tooltip>
<TooltipTrigger asChild>
- <div
- className="w-3 h-3 rounded-full border border-gray-300 dark:border-gray-600 cursor-help"
- style={{ backgroundColor: color.hex }}
- />
+ <div className="w-3 h-3 rounded-full bg-gray-300 dark:bg-gray-600 border border-gray-400 dark:border-gray-500 flex items-center justify-center text-xs font-bold text-gray-600 dark:text-gray-300 leading-none cursor-help">
+ +{pesData.uniqueColors.length - 8}
+ </div>
</TooltipTrigger>
- <TooltipContent className="max-w-xs">
- <p className="text-xs whitespace-pre-line">{tooltipText}</p>
+ <TooltipContent>
+ <p className="text-xs">
+ {pesData.uniqueColors.length - 8} more{" "}
+ {pesData.uniqueColors.length - 8 === 1 ? "color" : "colors"}
+ </p>
</TooltipContent>
</Tooltip>
- );
- })}
- {pesData.uniqueColors.length > 8 && (
- <Tooltip>
- <TooltipTrigger asChild>
- <div className="w-3 h-3 rounded-full bg-gray-300 dark:bg-gray-600 border border-gray-400 dark:border-gray-500 flex items-center justify-center text-xs font-bold text-gray-600 dark:text-gray-300 leading-none cursor-help">
- +{pesData.uniqueColors.length - 8}
- </div>
- </TooltipTrigger>
- <TooltipContent>
- <p className="text-xs">
- {pesData.uniqueColors.length - 8} more{" "}
- {pesData.uniqueColors.length - 8 === 1 ? "color" : "colors"}
- </p>
- </TooltipContent>
- </Tooltip>
- )}
+ )}
</div>
</TooltipProvider>
</div>
diff --git a/src/components/ProgressMonitor.tsx b/src/components/ProgressMonitor.tsx
index 1c9be6a..793afe6 100644
--- a/src/components/ProgressMonitor.tsx
+++ b/src/components/ProgressMonitor.tsx
@@ -17,7 +17,13 @@ import {
canResumeSewing,
} from "../utils/machineStateHelpers";
import { calculatePatternTime } from "../utils/timeCalculation";
-import { Card, CardHeader, CardTitle, CardDescription, CardContent } from "@/components/ui/card";
+import {
+ Card,
+ CardHeader,
+ CardTitle,
+ CardDescription,
+ CardContent,
+} from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Progress } from "@/components/ui/progress";
@@ -170,246 +176,246 @@ export function ProgressMonitor() {
</div>
</CardHeader>
<CardContent className="px-4 pt-0 pb-4 flex-1 flex flex-col lg:overflow-hidden">
-
- {/* Pattern Info */}
- {patternInfo && (
- <div className="grid grid-cols-3 gap-2 text-xs mb-3">
- <div className="bg-gray-200 dark:bg-gray-700/50 p-2 rounded">
- <span className="text-gray-600 dark:text-gray-400 block">
- Total Stitches
- </span>
- <span className="font-semibold text-gray-900 dark:text-gray-100">
- {totalStitches.toLocaleString()}
- </span>
- </div>
- <div className="bg-gray-200 dark:bg-gray-700/50 p-2 rounded">
- <span className="text-gray-600 dark:text-gray-400 block">
- Total Time
- </span>
- <span className="font-semibold text-gray-900 dark:text-gray-100">
- {totalMinutes} min
- </span>
- </div>
- <div className="bg-gray-200 dark:bg-gray-700/50 p-2 rounded">
- <span className="text-gray-600 dark:text-gray-400 block">
- Speed
- </span>
- <span className="font-semibold text-gray-900 dark:text-gray-100">
- {patternInfo.speed} spm
- </span>
- </div>
- </div>
- )}
-
- {/* Progress Bar */}
- {sewingProgress && (
- <div className="mb-3">
- <Progress
- value={progressPercent}
- className="h-3 mb-2 [&>div]:bg-gradient-to-r [&>div]:from-accent-600 [&>div]:to-accent-700 dark:[&>div]:from-accent-600 dark:[&>div]:to-accent-800"
- />
-
- <div className="grid grid-cols-2 gap-2 text-xs mb-3">
+ {/* Pattern Info */}
+ {patternInfo && (
+ <div className="grid grid-cols-3 gap-2 text-xs mb-3">
<div className="bg-gray-200 dark:bg-gray-700/50 p-2 rounded">
<span className="text-gray-600 dark:text-gray-400 block">
- Current Stitch
+ Total Stitches
</span>
<span className="font-semibold text-gray-900 dark:text-gray-100">
- {sewingProgress.currentStitch.toLocaleString()} /{" "}
{totalStitches.toLocaleString()}
</span>
</div>
<div className="bg-gray-200 dark:bg-gray-700/50 p-2 rounded">
<span className="text-gray-600 dark:text-gray-400 block">
- Time
+ Total Time
</span>
<span className="font-semibold text-gray-900 dark:text-gray-100">
- {elapsedMinutes} / {totalMinutes} min
+ {totalMinutes} min
+ </span>
+ </div>
+ <div className="bg-gray-200 dark:bg-gray-700/50 p-2 rounded">
+ <span className="text-gray-600 dark:text-gray-400 block">
+ Speed
+ </span>
+ <span className="font-semibold text-gray-900 dark:text-gray-100">
+ {patternInfo.speed} spm
</span>
</div>
</div>
- </div>
- )}
+ )}
- {/* Color Blocks */}
- {colorBlocks.length > 0 && (
- <div className="mb-3 lg:flex-1 lg:min-h-0 flex flex-col">
- <h4 className="text-xs font-semibold mb-2 text-gray-700 dark:text-gray-300 flex-shrink-0">
- Color Blocks
- </h4>
- <div className="relative lg:flex-1 lg:min-h-0">
- <div
- ref={colorBlocksScrollRef}
- onScroll={handleColorBlocksScroll}
- className="lg:absolute lg:inset-0 flex flex-col gap-2 lg:overflow-y-auto scroll-smooth pr-1 [&::-webkit-scrollbar]:w-1 [&::-webkit-scrollbar-track]:bg-gray-100 dark:[&::-webkit-scrollbar-track]:bg-gray-700 [&::-webkit-scrollbar-thumb]:bg-primary-600 dark:[&::-webkit-scrollbar-thumb]:bg-primary-500 [&::-webkit-scrollbar-thumb]:rounded-full"
- >
- {colorBlocks.map((block, index) => {
- const isCompleted = currentStitch >= block.endStitch;
- const isCurrent = index === currentBlockIndex;
+ {/* Progress Bar */}
+ {sewingProgress && (
+ <div className="mb-3">
+ <Progress
+ value={progressPercent}
+ className="h-3 mb-2 [&>div]:bg-gradient-to-r [&>div]:from-accent-600 [&>div]:to-accent-700 dark:[&>div]:from-accent-600 dark:[&>div]:to-accent-800"
+ />
+
+ <div className="grid grid-cols-2 gap-2 text-xs mb-3">
+ <div className="bg-gray-200 dark:bg-gray-700/50 p-2 rounded">
+ <span className="text-gray-600 dark:text-gray-400 block">
+ Current Stitch
+ </span>
+ <span className="font-semibold text-gray-900 dark:text-gray-100">
+ {sewingProgress.currentStitch.toLocaleString()} /{" "}
+ {totalStitches.toLocaleString()}
+ </span>
+ </div>
+ <div className="bg-gray-200 dark:bg-gray-700/50 p-2 rounded">
+ <span className="text-gray-600 dark:text-gray-400 block">
+ Time
+ </span>
+ <span className="font-semibold text-gray-900 dark:text-gray-100">
+ {elapsedMinutes} / {totalMinutes} min
+ </span>
+ </div>
+ </div>
+ </div>
+ )}
- // Calculate progress within current block
- let blockProgress = 0;
- if (isCurrent) {
- blockProgress =
- ((currentStitch - block.startStitch) / block.stitchCount) *
- 100;
- } else if (isCompleted) {
- blockProgress = 100;
- }
+ {/* Color Blocks */}
+ {colorBlocks.length > 0 && (
+ <div className="mb-3 lg:flex-1 lg:min-h-0 flex flex-col">
+ <h4 className="text-xs font-semibold mb-2 text-gray-700 dark:text-gray-300 flex-shrink-0">
+ Color Blocks
+ </h4>
+ <div className="relative lg:flex-1 lg:min-h-0">
+ <div
+ ref={colorBlocksScrollRef}
+ onScroll={handleColorBlocksScroll}
+ className="lg:absolute lg:inset-0 flex flex-col gap-2 lg:overflow-y-auto scroll-smooth pr-1 [&::-webkit-scrollbar]:w-1 [&::-webkit-scrollbar-track]:bg-gray-100 dark:[&::-webkit-scrollbar-track]:bg-gray-700 [&::-webkit-scrollbar-thumb]:bg-primary-600 dark:[&::-webkit-scrollbar-thumb]:bg-primary-500 [&::-webkit-scrollbar-thumb]:rounded-full"
+ >
+ {colorBlocks.map((block, index) => {
+ const isCompleted = currentStitch >= block.endStitch;
+ const isCurrent = index === currentBlockIndex;
- return (
- <div
- key={index}
- ref={isCurrent ? currentBlockRef : null}
- className={`p-2.5 rounded-lg border-2 transition-all duration-300 ${
- isCompleted
- ? "border-success-600 bg-success-50 dark:bg-success-900/20"
- : isCurrent
- ? "border-gray-400 dark:border-gray-500 bg-white dark:bg-gray-700"
- : "border-gray-200 dark:border-gray-600 bg-gray-100 dark:bg-gray-800/50 opacity-70"
- }`}
- role="listitem"
- aria-label={`Thread ${block.colorIndex + 1}, ${block.stitchCount} stitches, ${isCompleted ? "completed" : isCurrent ? "in progress" : "pending"}`}
- >
- <div className="flex items-center gap-2.5">
- {/* Color swatch */}
- <div
- className="w-7 h-7 rounded-lg border-2 border-gray-300 dark:border-gray-600 shadow-md flex-shrink-0"
- style={{
- backgroundColor: block.threadHex,
- }}
- title={`Thread color: ${block.threadHex}`}
- aria-label={`Thread color ${block.threadHex}`}
- />
+ // Calculate progress within current block
+ let blockProgress = 0;
+ if (isCurrent) {
+ blockProgress =
+ ((currentStitch - block.startStitch) /
+ block.stitchCount) *
+ 100;
+ } else if (isCompleted) {
+ blockProgress = 100;
+ }
- {/* Thread info */}
- <div className="flex-1 min-w-0">
- <div className="font-semibold text-xs text-gray-900 dark:text-gray-100">
- Thread {block.colorIndex + 1}
- {(block.threadBrand ||
- block.threadChart ||
- block.threadDescription ||
- block.threadCatalogNumber) && (
- <span className="font-normal text-gray-600 dark:text-gray-400">
- {" "}
- (
- {(() => {
- // Primary metadata: brand and catalog number
- const primaryMetadata = [
- block.threadBrand,
- block.threadCatalogNumber
- ? `#${block.threadCatalogNumber}`
- : null,
- ]
- .filter(Boolean)
- .join(" ");
+ return (
+ <div
+ key={index}
+ ref={isCurrent ? currentBlockRef : null}
+ className={`p-2.5 rounded-lg border-2 transition-all duration-300 ${
+ isCompleted
+ ? "border-success-600 bg-success-50 dark:bg-success-900/20"
+ : isCurrent
+ ? "border-gray-400 dark:border-gray-500 bg-white dark:bg-gray-700"
+ : "border-gray-200 dark:border-gray-600 bg-gray-100 dark:bg-gray-800/50 opacity-70"
+ }`}
+ role="listitem"
+ aria-label={`Thread ${block.colorIndex + 1}, ${block.stitchCount} stitches, ${isCompleted ? "completed" : isCurrent ? "in progress" : "pending"}`}
+ >
+ <div className="flex items-center gap-2.5">
+ {/* Color swatch */}
+ <div
+ className="w-7 h-7 rounded-lg border-2 border-gray-300 dark:border-gray-600 shadow-md flex-shrink-0"
+ style={{
+ backgroundColor: block.threadHex,
+ }}
+ title={`Thread color: ${block.threadHex}`}
+ aria-label={`Thread color ${block.threadHex}`}
+ />
- // Secondary metadata: chart and description
- const secondaryMetadata = [
- block.threadChart,
- block.threadDescription,
- ]
- .filter(Boolean)
- .join(" ");
+ {/* Thread info */}
+ <div className="flex-1 min-w-0">
+ <div className="font-semibold text-xs text-gray-900 dark:text-gray-100">
+ Thread {block.colorIndex + 1}
+ {(block.threadBrand ||
+ block.threadChart ||
+ block.threadDescription ||
+ block.threadCatalogNumber) && (
+ <span className="font-normal text-gray-600 dark:text-gray-400">
+ {" "}
+ (
+ {(() => {
+ // Primary metadata: brand and catalog number
+ const primaryMetadata = [
+ block.threadBrand,
+ block.threadCatalogNumber
+ ? `#${block.threadCatalogNumber}`
+ : null,
+ ]
+ .filter(Boolean)
+ .join(" ");
- return [primaryMetadata, secondaryMetadata]
- .filter(Boolean)
- .join(" • ");
- })()}
- )
- </span>
- )}
- </div>
- <div className="text-xs text-gray-600 dark:text-gray-400 mt-0.5">
- {block.stitchCount.toLocaleString()} stitches
+ // Secondary metadata: chart and description
+ const secondaryMetadata = [
+ block.threadChart,
+ block.threadDescription,
+ ]
+ .filter(Boolean)
+ .join(" ");
+
+ return [primaryMetadata, secondaryMetadata]
+ .filter(Boolean)
+ .join(" • ");
+ })()}
+ )
+ </span>
+ )}
+ </div>
+ <div className="text-xs text-gray-600 dark:text-gray-400 mt-0.5">
+ {block.stitchCount.toLocaleString()} stitches
+ </div>
</div>
+
+ {/* Status icon */}
+ {isCompleted ? (
+ <CheckCircleIcon
+ className="w-5 h-5 text-success-600 flex-shrink-0"
+ aria-label="Completed"
+ />
+ ) : isCurrent ? (
+ <ArrowRightIcon
+ className="w-5 h-5 text-gray-600 dark:text-gray-400 flex-shrink-0 animate-pulse"
+ aria-label="In progress"
+ />
+ ) : (
+ <CircleStackIcon
+ className="w-5 h-5 text-gray-400 flex-shrink-0"
+ aria-label="Pending"
+ />
+ )}
</div>
- {/* Status icon */}
- {isCompleted ? (
- <CheckCircleIcon
- className="w-5 h-5 text-success-600 flex-shrink-0"
- aria-label="Completed"
- />
- ) : isCurrent ? (
- <ArrowRightIcon
- className="w-5 h-5 text-gray-600 dark:text-gray-400 flex-shrink-0 animate-pulse"
- aria-label="In progress"
- />
- ) : (
- <CircleStackIcon
- className="w-5 h-5 text-gray-400 flex-shrink-0"
- aria-label="Pending"
+ {/* Progress bar for current block */}
+ {isCurrent && (
+ <Progress
+ value={blockProgress}
+ className="mt-2 h-1.5 [&>div]:bg-gray-600 dark:[&>div]:bg-gray-500"
+ aria-label={`${Math.round(blockProgress)}% complete`}
/>
)}
</div>
-
- {/* Progress bar for current block */}
- {isCurrent && (
- <Progress
- value={blockProgress}
- className="mt-2 h-1.5 [&>div]:bg-gray-600 dark:[&>div]:bg-gray-500"
- aria-label={`${Math.round(blockProgress)}% complete`}
- />
- )}
- </div>
- );
- })}
+ );
+ })}
+ </div>
+ {/* Gradient overlay to indicate more content below - only on desktop and when not at bottom */}
+ {showGradient && (
+ <div className="hidden lg:block absolute bottom-0 left-0 right-0 h-8 bg-gradient-to-t from-white dark:from-gray-800 to-transparent pointer-events-none" />
+ )}
</div>
- {/* Gradient overlay to indicate more content below - only on desktop and when not at bottom */}
- {showGradient && (
- <div className="hidden lg:block absolute bottom-0 left-0 right-0 h-8 bg-gradient-to-t from-white dark:from-gray-800 to-transparent pointer-events-none" />
- )}
</div>
- </div>
- )}
-
- {/* Action buttons */}
- <div className="flex gap-2 flex-shrink-0">
- {/* Resume has highest priority when available */}
- {canResumeSewing(machineStatus) && (
- <Button
- onClick={resumeSewing}
- disabled={isDeleting}
- className="flex-1"
- aria-label="Resume sewing the current pattern"
- >
- <PlayIcon className="w-3.5 h-3.5" />
- Resume Sewing
- </Button>
)}
- {/* Start Sewing - primary action, takes more space */}
- {canStartSewing(machineStatus) && !canResumeSewing(machineStatus) && (
- <Button
- onClick={startSewing}
- disabled={isDeleting}
- className="flex-[2]"
- aria-label="Start sewing the pattern"
- >
- <PlayIcon className="w-3.5 h-3.5" />
- Start Sewing
- </Button>
- )}
+ {/* Action buttons */}
+ <div className="flex gap-2 flex-shrink-0">
+ {/* Resume has highest priority when available */}
+ {canResumeSewing(machineStatus) && (
+ <Button
+ onClick={resumeSewing}
+ disabled={isDeleting}
+ className="flex-1"
+ aria-label="Resume sewing the current pattern"
+ >
+ <PlayIcon className="w-3.5 h-3.5" />
+ Resume Sewing
+ </Button>
+ )}
- {/* Start Mask Trace - secondary action */}
- {canStartMaskTrace(machineStatus) && (
- <Button
- onClick={startMaskTrace}
- disabled={isDeleting}
- variant="outline"
- className="flex-1"
- aria-label={
- isMaskTraceComplete
- ? "Start mask trace again"
- : "Start mask trace"
- }
- >
- <ArrowPathIcon className="w-3.5 h-3.5" />
- {isMaskTraceComplete ? "Trace Again" : "Start Mask Trace"}
- </Button>
- )}
- </div>
+ {/* Start Sewing - primary action, takes more space */}
+ {canStartSewing(machineStatus) && !canResumeSewing(machineStatus) && (
+ <Button
+ onClick={startSewing}
+ disabled={isDeleting}
+ className="flex-[2]"
+ aria-label="Start sewing the pattern"
+ >
+ <PlayIcon className="w-3.5 h-3.5" />
+ Start Sewing
+ </Button>
+ )}
+
+ {/* Start Mask Trace - secondary action */}
+ {canStartMaskTrace(machineStatus) && (
+ <Button
+ onClick={startMaskTrace}
+ disabled={isDeleting}
+ variant="outline"
+ className="flex-1"
+ aria-label={
+ isMaskTraceComplete
+ ? "Start mask trace again"
+ : "Start mask trace"
+ }
+ >
+ <ArrowPathIcon className="w-3.5 h-3.5" />
+ {isMaskTraceComplete ? "Trace Again" : "Start Mask Trace"}
+ </Button>
+ )}
+ </div>
</CardContent>
</Card>
);
diff --git a/src/components/SkeletonLoader.tsx b/src/components/SkeletonLoader.tsx
index a69a655..3da170d 100644
--- a/src/components/SkeletonLoader.tsx
+++ b/src/components/SkeletonLoader.tsx
@@ -16,9 +16,7 @@ export function SkeletonLoader({
circle: "rounded-full",
};
- return (
- <Skeleton className={cn(variantClasses[variant], className)} />
- );
+ return <Skeleton className={cn(variantClasses[variant], className)} />;
}
export function PatternCanvasSkeleton() {
diff --git a/src/components/ui/alert-dialog.tsx b/src/components/ui/alert-dialog.tsx
index 935eecf..5284517 100644
--- a/src/components/ui/alert-dialog.tsx
+++ b/src/components/ui/alert-dialog.tsx
@@ -1,13 +1,13 @@
-import * as React from "react"
-import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"
+import * as React from "react";
+import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog";
-import { cn } from "@/lib/utils"
-import { buttonVariants } from "@/components/ui/button"
+import { cn } from "@/lib/utils";
+import { buttonVariants } from "@/components/ui/button";
function AlertDialog({
...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Root>) {
- return <AlertDialogPrimitive.Root data-slot="alert-dialog" {...props} />
+ return <AlertDialogPrimitive.Root data-slot="alert-dialog" {...props} />;
}
function AlertDialogTrigger({
@@ -15,7 +15,7 @@ function AlertDialogTrigger({
}: React.ComponentProps<typeof AlertDialogPrimitive.Trigger>) {
return (
<AlertDialogPrimitive.Trigger data-slot="alert-dialog-trigger" {...props} />
- )
+ );
}
function AlertDialogPortal({
@@ -23,7 +23,7 @@ function AlertDialogPortal({
}: React.ComponentProps<typeof AlertDialogPrimitive.Portal>) {
return (
<AlertDialogPrimitive.Portal data-slot="alert-dialog-portal" {...props} />
- )
+ );
}
function AlertDialogOverlay({
@@ -35,11 +35,11 @@ function AlertDialogOverlay({
data-slot="alert-dialog-overlay"
className={cn(
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
- className
+ className,
)}
{...props}
/>
- )
+ );
}
function AlertDialogContent({
@@ -53,12 +53,12 @@ function AlertDialogContent({
data-slot="alert-dialog-content"
className={cn(
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg",
- className
+ className,
)}
{...props}
/>
</AlertDialogPortal>
- )
+ );
}
function AlertDialogHeader({
@@ -71,7 +71,7 @@ function AlertDialogHeader({
className={cn("flex flex-col gap-2 text-center sm:text-left", className)}
{...props}
/>
- )
+ );
}
function AlertDialogFooter({
@@ -83,11 +83,11 @@ function AlertDialogFooter({
data-slot="alert-dialog-footer"
className={cn(
"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
- className
+ className,
)}
{...props}
/>
- )
+ );
}
function AlertDialogTitle({
@@ -100,7 +100,7 @@ function AlertDialogTitle({
className={cn("text-lg font-semibold", className)}
{...props}
/>
- )
+ );
}
function AlertDialogDescription({
@@ -113,7 +113,7 @@ function AlertDialogDescription({
className={cn("text-muted-foreground text-sm", className)}
{...props}
/>
- )
+ );
}
function AlertDialogAction({
@@ -125,7 +125,7 @@ function AlertDialogAction({
className={cn(buttonVariants(), className)}
{...props}
/>
- )
+ );
}
function AlertDialogCancel({
@@ -137,7 +137,7 @@ function AlertDialogCancel({
className={cn(buttonVariants({ variant: "outline" }), className)}
{...props}
/>
- )
+ );
}
export {
@@ -152,4 +152,4 @@ export {
AlertDialogDescription,
AlertDialogAction,
AlertDialogCancel,
-}
+};
diff --git a/src/components/ui/alert.tsx b/src/components/ui/alert.tsx
index 1421354..aa7de24 100644
--- a/src/components/ui/alert.tsx
+++ b/src/components/ui/alert.tsx
@@ -1,7 +1,7 @@
-import * as React from "react"
-import { cva, type VariantProps } from "class-variance-authority"
+import * as React from "react";
+import { cva, type VariantProps } from "class-variance-authority";
-import { cn } from "@/lib/utils"
+import { cn } from "@/lib/utils";
const alertVariants = cva(
"relative w-full rounded-lg border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current",
@@ -16,8 +16,8 @@ const alertVariants = cva(
defaultVariants: {
variant: "default",
},
- }
-)
+ },
+);
function Alert({
className,
@@ -31,7 +31,7 @@ function Alert({
className={cn(alertVariants({ variant }), className)}
{...props}
/>
- )
+ );
}
function AlertTitle({ className, ...props }: React.ComponentProps<"div">) {
@@ -40,11 +40,11 @@ function AlertTitle({ className, ...props }: React.ComponentProps<"div">) {
data-slot="alert-title"
className={cn(
"col-start-2 line-clamp-1 min-h-4 font-medium tracking-tight",
- className
+ className,
)}
{...props}
/>
- )
+ );
}
function AlertDescription({
@@ -56,11 +56,11 @@ function AlertDescription({
data-slot="alert-description"
className={cn(
"text-muted-foreground col-start-2 grid justify-items-start gap-1 text-sm [&_p]:leading-relaxed",
- className
+ className,
)}
{...props}
/>
- )
+ );
}
-export { Alert, AlertTitle, AlertDescription }
+export { Alert, AlertTitle, AlertDescription };
diff --git a/src/components/ui/badge.tsx b/src/components/ui/badge.tsx
index fd3a406..55f8352 100644
--- a/src/components/ui/badge.tsx
+++ b/src/components/ui/badge.tsx
@@ -1,8 +1,8 @@
-import * as React from "react"
-import { Slot } from "@radix-ui/react-slot"
-import { cva, type VariantProps } from "class-variance-authority"
+import * as React from "react";
+import { Slot } from "@radix-ui/react-slot";
+import { cva, type VariantProps } from "class-variance-authority";
-import { cn } from "@/lib/utils"
+import { cn } from "@/lib/utils";
const badgeVariants = cva(
"inline-flex items-center justify-center rounded-full border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden",
@@ -22,8 +22,8 @@ const badgeVariants = cva(
defaultVariants: {
variant: "default",
},
- }
-)
+ },
+);
function Badge({
className,
@@ -32,7 +32,7 @@ function Badge({
...props
}: React.ComponentProps<"span"> &
VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
- const Comp = asChild ? Slot : "span"
+ const Comp = asChild ? Slot : "span";
return (
<Comp
@@ -40,7 +40,7 @@ function Badge({
className={cn(badgeVariants({ variant }), className)}
{...props}
/>
- )
+ );
}
-export { Badge, badgeVariants }
+export { Badge, badgeVariants };
diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx
index fad5e69..51bc4ae 100644
--- a/src/components/ui/button.tsx
+++ b/src/components/ui/button.tsx
@@ -1,8 +1,8 @@
-import * as React from "react"
-import { Slot } from "@radix-ui/react-slot"
-import { cva, type VariantProps } from "class-variance-authority"
+import * as React from "react";
+import { Slot } from "@radix-ui/react-slot";
+import { cva, type VariantProps } from "class-variance-authority";
-import { cn } from "@/lib/utils"
+import { cn } from "@/lib/utils";
const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all cursor-pointer disabled:cursor-not-allowed disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
@@ -33,8 +33,8 @@ const buttonVariants = cva(
variant: "default",
size: "default",
},
- }
-)
+ },
+);
function Button({
className,
@@ -44,9 +44,9 @@ function Button({
...props
}: React.ComponentProps<"button"> &
VariantProps<typeof buttonVariants> & {
- asChild?: boolean
+ asChild?: boolean;
}) {
- const Comp = asChild ? Slot : "button"
+ const Comp = asChild ? Slot : "button";
return (
<Comp
@@ -56,7 +56,7 @@ function Button({
className={cn(buttonVariants({ variant, size, className }))}
{...props}
/>
- )
+ );
}
-export { Button, buttonVariants }
+export { Button, buttonVariants };
diff --git a/src/components/ui/card.tsx b/src/components/ui/card.tsx
index 681ad98..4f88024 100644
--- a/src/components/ui/card.tsx
+++ b/src/components/ui/card.tsx
@@ -1,6 +1,6 @@
-import * as React from "react"
+import * as React from "react";
-import { cn } from "@/lib/utils"
+import { cn } from "@/lib/utils";
function Card({ className, ...props }: React.ComponentProps<"div">) {
return (
@@ -8,11 +8,11 @@ function Card({ className, ...props }: React.ComponentProps<"div">) {
data-slot="card"
className={cn(
"bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm",
- className
+ className,
)}
{...props}
/>
- )
+ );
}
function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
@@ -21,11 +21,11 @@ function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
data-slot="card-header"
className={cn(
"@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-2 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",
- className
+ className,
)}
{...props}
/>
- )
+ );
}
function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
@@ -35,7 +35,7 @@ function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
className={cn("leading-none font-semibold", className)}
{...props}
/>
- )
+ );
}
function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
@@ -45,7 +45,7 @@ function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
className={cn("text-muted-foreground text-sm", className)}
{...props}
/>
- )
+ );
}
function CardAction({ className, ...props }: React.ComponentProps<"div">) {
@@ -54,11 +54,11 @@ function CardAction({ className, ...props }: React.ComponentProps<"div">) {
data-slot="card-action"
className={cn(
"col-start-2 row-span-2 row-start-1 self-start justify-self-end",
- className
+ className,
)}
{...props}
/>
- )
+ );
}
function CardContent({ className, ...props }: React.ComponentProps<"div">) {
@@ -68,7 +68,7 @@ function CardContent({ className, ...props }: React.ComponentProps<"div">) {
className={cn("px-6", className)}
{...props}
/>
- )
+ );
}
function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
@@ -78,7 +78,7 @@ function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
className={cn("flex items-center px-6 [.border-t]:pt-6", className)}
{...props}
/>
- )
+ );
}
export {
@@ -89,4 +89,4 @@ export {
CardAction,
CardDescription,
CardContent,
-}
+};
diff --git a/src/components/ui/dialog.tsx b/src/components/ui/dialog.tsx
index 60cc10e..a701d5b 100644
--- a/src/components/ui/dialog.tsx
+++ b/src/components/ui/dialog.tsx
@@ -1,31 +1,31 @@
-import * as React from "react"
-import * as DialogPrimitive from "@radix-ui/react-dialog"
-import { XIcon } from "lucide-react"
+import * as React from "react";
+import * as DialogPrimitive from "@radix-ui/react-dialog";
+import { XIcon } from "lucide-react";
-import { cn } from "@/lib/utils"
+import { cn } from "@/lib/utils";
function Dialog({
...props
}: React.ComponentProps<typeof DialogPrimitive.Root>) {
- return <DialogPrimitive.Root data-slot="dialog" {...props} />
+ return <DialogPrimitive.Root data-slot="dialog" {...props} />;
}
function DialogTrigger({
...props
}: React.ComponentProps<typeof DialogPrimitive.Trigger>) {
- return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />
+ return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />;
}
function DialogPortal({
...props
}: React.ComponentProps<typeof DialogPrimitive.Portal>) {
- return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />
+ return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />;
}
function DialogClose({
...props
}: React.ComponentProps<typeof DialogPrimitive.Close>) {
- return <DialogPrimitive.Close data-slot="dialog-close" {...props} />
+ return <DialogPrimitive.Close data-slot="dialog-close" {...props} />;
}
function DialogOverlay({
@@ -37,11 +37,11 @@ function DialogOverlay({
data-slot="dialog-overlay"
className={cn(
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
- className
+ className,
)}
{...props}
/>
- )
+ );
}
function DialogContent({
@@ -50,7 +50,7 @@ function DialogContent({
showCloseButton = true,
...props
}: React.ComponentProps<typeof DialogPrimitive.Content> & {
- showCloseButton?: boolean
+ showCloseButton?: boolean;
}) {
return (
<DialogPortal data-slot="dialog-portal">
@@ -59,7 +59,7 @@ function DialogContent({
data-slot="dialog-content"
className={cn(
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 outline-none sm:max-w-lg",
- className
+ className,
)}
{...props}
>
@@ -75,7 +75,7 @@ function DialogContent({
)}
</DialogPrimitive.Content>
</DialogPortal>
- )
+ );
}
function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
@@ -85,7 +85,7 @@ function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
className={cn("flex flex-col gap-2 text-center sm:text-left", className)}
{...props}
/>
- )
+ );
}
function DialogFooter({ className, ...props }: React.ComponentProps<"div">) {
@@ -94,11 +94,11 @@ function DialogFooter({ className, ...props }: React.ComponentProps<"div">) {
data-slot="dialog-footer"
className={cn(
"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
- className
+ className,
)}
{...props}
/>
- )
+ );
}
function DialogTitle({
@@ -111,7 +111,7 @@ function DialogTitle({
className={cn("text-lg leading-none font-semibold", className)}
{...props}
/>
- )
+ );
}
function DialogDescription({
@@ -124,7 +124,7 @@ function DialogDescription({
className={cn("text-muted-foreground text-sm", className)}
{...props}
/>
- )
+ );
}
export {
@@ -138,4 +138,4 @@ export {
DialogPortal,
DialogTitle,
DialogTrigger,
-}
+};
diff --git a/src/components/ui/popover.tsx b/src/components/ui/popover.tsx
index 01e468b..5d6f78d 100644
--- a/src/components/ui/popover.tsx
+++ b/src/components/ui/popover.tsx
@@ -1,20 +1,20 @@
-"use client"
+"use client";
-import * as React from "react"
-import * as PopoverPrimitive from "@radix-ui/react-popover"
+import * as React from "react";
+import * as PopoverPrimitive from "@radix-ui/react-popover";
-import { cn } from "@/lib/utils"
+import { cn } from "@/lib/utils";
function Popover({
...props
}: React.ComponentProps<typeof PopoverPrimitive.Root>) {
- return <PopoverPrimitive.Root data-slot="popover" {...props} />
+ return <PopoverPrimitive.Root data-slot="popover" {...props} />;
}
function PopoverTrigger({
...props
}: React.ComponentProps<typeof PopoverPrimitive.Trigger>) {
- return <PopoverPrimitive.Trigger data-slot="popover-trigger" {...props} />
+ return <PopoverPrimitive.Trigger data-slot="popover-trigger" {...props} />;
}
function PopoverContent({
@@ -31,18 +31,18 @@ function PopoverContent({
sideOffset={sideOffset}
className={cn(
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-72 origin-(--radix-popover-content-transform-origin) rounded-md border p-4 shadow-md outline-hidden",
- className
+ className,
)}
{...props}
/>
</PopoverPrimitive.Portal>
- )
+ );
}
function PopoverAnchor({
...props
}: React.ComponentProps<typeof PopoverPrimitive.Anchor>) {
- return <PopoverPrimitive.Anchor data-slot="popover-anchor" {...props} />
+ return <PopoverPrimitive.Anchor data-slot="popover-anchor" {...props} />;
}
-export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor }
+export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor };
diff --git a/src/components/ui/progress.tsx b/src/components/ui/progress.tsx
index e7a416c..e435637 100644
--- a/src/components/ui/progress.tsx
+++ b/src/components/ui/progress.tsx
@@ -1,9 +1,9 @@
-"use client"
+"use client";
-import * as React from "react"
-import * as ProgressPrimitive from "@radix-ui/react-progress"
+import * as React from "react";
+import * as ProgressPrimitive from "@radix-ui/react-progress";
-import { cn } from "@/lib/utils"
+import { cn } from "@/lib/utils";
function Progress({
className,
@@ -15,7 +15,7 @@ function Progress({
data-slot="progress"
className={cn(
"bg-primary/20 relative h-2 w-full overflow-hidden rounded-full",
- className
+ className,
)}
{...props}
>
@@ -25,7 +25,7 @@ function Progress({
style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
/>
</ProgressPrimitive.Root>
- )
+ );
}
-export { Progress }
+export { Progress };
diff --git a/src/components/ui/separator.tsx b/src/components/ui/separator.tsx
index bb3ad74..b246f3a 100644
--- a/src/components/ui/separator.tsx
+++ b/src/components/ui/separator.tsx
@@ -1,7 +1,7 @@
-import * as React from "react"
-import * as SeparatorPrimitive from "@radix-ui/react-separator"
+import * as React from "react";
+import * as SeparatorPrimitive from "@radix-ui/react-separator";
-import { cn } from "@/lib/utils"
+import { cn } from "@/lib/utils";
function Separator({
className,
@@ -16,11 +16,11 @@ function Separator({
orientation={orientation}
className={cn(
"bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px",
- className
+ className,
)}
{...props}
/>
- )
+ );
}
-export { Separator }
+export { Separator };
diff --git a/src/components/ui/skeleton.tsx b/src/components/ui/skeleton.tsx
index 32ea0ef..0168998 100644
--- a/src/components/ui/skeleton.tsx
+++ b/src/components/ui/skeleton.tsx
@@ -1,4 +1,4 @@
-import { cn } from "@/lib/utils"
+import { cn } from "@/lib/utils";
function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
return (
@@ -7,7 +7,7 @@ function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
className={cn("bg-accent animate-pulse rounded-md", className)}
{...props}
/>
- )
+ );
}
-export { Skeleton }
+export { Skeleton };
diff --git a/src/components/ui/tooltip.tsx b/src/components/ui/tooltip.tsx
index 715bf76..b572cd3 100644
--- a/src/components/ui/tooltip.tsx
+++ b/src/components/ui/tooltip.tsx
@@ -1,7 +1,7 @@
-import * as React from "react"
-import * as TooltipPrimitive from "@radix-ui/react-tooltip"
+import * as React from "react";
+import * as TooltipPrimitive from "@radix-ui/react-tooltip";
-import { cn } from "@/lib/utils"
+import { cn } from "@/lib/utils";
function TooltipProvider({
delayDuration = 0,
@@ -13,7 +13,7 @@ function TooltipProvider({
delayDuration={delayDuration}
{...props}
/>
- )
+ );
}
function Tooltip({
@@ -23,13 +23,13 @@ function Tooltip({
<TooltipProvider>
<TooltipPrimitive.Root data-slot="tooltip" {...props} />
</TooltipProvider>
- )
+ );
}
function TooltipTrigger({
...props
}: React.ComponentProps<typeof TooltipPrimitive.Trigger>) {
- return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />
+ return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />;
}
function TooltipContent({
@@ -45,7 +45,7 @@ function TooltipContent({
sideOffset={sideOffset}
className={cn(
"bg-foreground text-background animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-fit origin-(--radix-tooltip-content-transform-origin) rounded-md px-3 py-1.5 text-xs text-balance",
- className
+ className,
)}
{...props}
>
@@ -53,7 +53,7 @@ function TooltipContent({
<TooltipPrimitive.Arrow className="bg-foreground fill-foreground z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px]" />
</TooltipPrimitive.Content>
</TooltipPrimitive.Portal>
- )
+ );
}
-export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
+export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
diff --git a/src/lib/utils.ts b/src/lib/utils.ts
index d084cca..365058c 100644
--- a/src/lib/utils.ts
+++ b/src/lib/utils.ts
@@ -1,6 +1,6 @@
-import { type ClassValue, clsx } from "clsx"
-import { twMerge } from "tailwind-merge"
+import { type ClassValue, clsx } from "clsx";
+import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
- return twMerge(clsx(inputs))
+ return twMerge(clsx(inputs));
}
Served by rngit 1.3.3 - Generated in 0.19s